﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace InterLinq.Types {
	/// <summary>
	/// InterLINQ representation of <see cref="Type"/>.
	/// </summary>
	/// <seealso cref="InterLinqMemberInfo"/>
	/// <seealso cref="Type"/>
	[Serializable]
	[DataContract]
	public class InterLinqType : InterLinqMemberInfo {
		#region Properties

		private List<InterLinqType> m_genericArguments = new List<InterLinqType>();
		private bool m_isGeneric;
		private Type m_representedType;

		/// <summary>
		/// Gets or sets if this is a generic <see cref="Type"/>.
		/// </summary>
		/// <seealso cref="Type.IsGenericType"/>
		[DataMember]
		public virtual bool IsGeneric {
			get { return m_isGeneric; }
			set { m_isGeneric = value; }
		}

		/// <summary>
		/// Gets the <see cref="MemberTypes">MemberType</see>.
		/// </summary>
		/// <seealso cref="Type.MemberType"/>
		public override MemberTypes MemberType {
			get { return MemberTypes.TypeInfo; }
		}

		/// <summary>
		/// Gets or sets the represented <see cref="Type"/>.
		/// </summary>
		[DataMember]
		public Type RepresentedType {
			get { return m_representedType; }
			set { m_representedType = value; }
		}

		/// <summary>
		/// Gets or sets the generic Arguments.
		/// </summary>
		/// <seealso cref="Type.GetGenericArguments"/>
		[DataMember]
		public List<InterLinqType> GenericArguments {
			get { return m_genericArguments; }
			set { m_genericArguments = value; }
		}

		#endregion

		#region Constructors / Initialization

		/// <summary>
		/// Empty constructor.
		/// </summary>
		public InterLinqType() {
		}

		/// <summary>
		/// Initializes this class.
		/// </summary>
		/// <param name="representedType">Represented CLR <see cref="Type"/>.</param>
		public InterLinqType(Type representedType)
			: this() {
			Initialize(representedType);
		}

		/// <summary>
		/// Initializes this class.
		/// </summary>
		/// <param name="memberInfo">Represented <see cref="MemberInfo"/></param>
		/// <seealso cref="InterLinqMemberInfo.Initialize"/>
		public override void Initialize(MemberInfo memberInfo) {
			var representedType = memberInfo as Type;
			Name = representedType.Name;
			if (representedType.IsGenericType) {
				m_representedType = representedType.GetGenericTypeDefinition();
				m_isGeneric = true;
				foreach (Type genericArgument in representedType.GetGenericArguments()) {
					m_genericArguments.Add(InterLinqTypeSystem.Instance.GetInterLinqVersionOf<InterLinqType>(genericArgument));
				}
			}
			else {
				m_representedType = representedType;
				m_isGeneric = false;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Creates and returns the CLR <see cref="Type"/>.
		/// </summary>
		/// <returns>Creates and returns the CLR <see cref="Type"/>.</returns>
		protected virtual Type CreateClrType() {
			if (!m_isGeneric) {
				return m_representedType;
			}
			return m_representedType.MakeGenericType(m_genericArguments.Select(arg => (Type)arg.GetClrVersion()).ToArray());
		}

		/// <summary>
		/// Returns the CLR <see cref="MemberInfo"/>.
		/// </summary>
		/// <returns>Returns the CLR <see cref="MemberInfo"/>.</returns>
		public override MemberInfo GetClrVersion() {
			InterLinqTypeSystem tsInstance = InterLinqTypeSystem.Instance;
			lock (tsInstance) {
				if (tsInstance.IsInterLinqMemberInfoRegistered(this)) {
					return tsInstance.GetClrVersion<Type>(this);
				}
				Type createdType = CreateClrType();
				tsInstance.SetClrVersion(this, createdType);
				return createdType;
			}
		}

		/// <summary>
		/// Compares <paramref name="obj"/> to this instance.
		/// </summary>
		/// <param name="obj"><see langword="object"/> to compare.</param>
		/// <returns>True if equal, false if not.</returns>
		public override bool Equals(object obj) {
			if (obj == null || GetType() != obj.GetType()) {
				return false;
			}
			var other = (InterLinqType)obj;
			if (m_genericArguments.Count != other.m_genericArguments.Count) {
				return false;
			}
			for (int i = 0; i < m_genericArguments.Count; i++) {
				if (!m_genericArguments[i].Equals(other.m_genericArguments[i])) {
					return false;
				}
			}
			return MemberType == other.MemberType && m_representedType == other.m_representedType && Name == other.Name &&
				   m_isGeneric == other.m_isGeneric;
		}

		/// <summary>
		/// Serves as a hash function for a particular type.
		/// </summary>
		/// <returns>A hash code for the current <see langword="object"/>.</returns>
		public override int GetHashCode() {
			int num = -657803396;
			num ^= EqualityComparer<bool>.Default.GetHashCode(m_isGeneric);
			num ^= EqualityComparer<Type>.Default.GetHashCode(m_representedType);
			num ^= EqualityComparer<List<InterLinqType>>.Default.GetHashCode(m_genericArguments);
			return num ^ base.GetHashCode();
		}

		#endregion
	}
}